#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys, os, struct, array, copy
import cPickle
import math

import nmr
import singleton

try:
 	import pygtk
  	pygtk.require("2.24")
except:
  	pass

try:
	import gtk
  	import gtk.glade
  	
except:
	print "gtk und/oder gtk.glade nicht gefunden."
	sys.exit(1)

#try:
#	import scipy.fftpack
#except:
#	print "scipy.fftpack nicht gefunden."
#	sys.exit(1)

class Workspace(singleton.Singleton):
	"""Beinhaltet alle Sachen, die den Workspace angehen: Verwaltung der Einstellungen... Und so halt."""
	oMain = None
	oSelf = None
	
	sFilepath = None
	bFileOpen = False
	oFile = None
	
	aWorkspaceData = {}
	
	pixmap = None
	loadedNMR = None
	
	# Statische Dinge
	iPickleProtocol = 2
	
	def __init__(self, main):
		""" Initialisiert den Singleton-Pattern "Workspace" """
		singleton.Singleton.__init__(self)
		
		# Mutter-Objekt speichern
		self.oMain = main
		
	def clear(self):
		""" Bereitet das Objekt für ein erneutes Laden/Erstellen vor. """
		self.sFilepath = None
		self.aWorkspaceData = {}
		self.oFile = None
		self.oMain.addFilenameToTitle()
		
	def create(self, filepath, nmrpath):
		if os.path.exists(filepath):
			if os.path.isfile(filepath):
				# Datei löschen (gegengefragt haben wir ja)
				os.unlink(filepath)
				# Erstellung starten
				self.startCreation(filepath, nmrpath)
			else:
				raise Exception('Die angegebene Datei ist keine Datei. :)')
		else:
			self.startCreation(filepath, nmrpath)
			
	def startCreation(self, filepath, nmrpath):
		print "Erstelle Datei " + filepath
		# Organisatorisches: Name in den Fenstertitel einfügen
		self.oMain.addFilenameToTitle(filepath)
		
		# Dateipfad (endlich) im Objekt speichern
		self.sFilepath = filepath
		
		# Datei öffnen
		self.oFile = open(self.sFilepath, 'wb')
		self.bFileOpen = True
		
		# Grundlegendes Workspace-Array kreieren:
		self.aWorkspaceData = {
			'nmrpath' : nmrpath,
		}
		
		cPickle.dump(self.aWorkspaceData, self.oFile)
		self.oFile.close()
		self.oFile = open(self.sFilepath, 'r+b')
		
		# Bereite Workspace vor
		self.prepareWorkspace()
			
	def open(self, filepath):
		# Wenn bereits eine Datei offen ist, wird sie nun halt geschlossen und gespeichert
		if self.bFileOpen == True:
			self.close()
			self.bFileOpen = False
		
		if os.path.exists(filepath):
			self.startLoading(filepath)
		else:
			raise Exception('Die angegebene Datei existiert gar nicht. Kann da nix tun...')
			
	def startLoading(self, filepath):
		self.oMain.addFilenameToTitle(filepath)
		self.sFilepath = filepath
		self.oFile = open(self.sFilepath, 'r+b')
		
		try:
			self.aWorkspaceData = cPickle.load(self.oFile)
			self.oFile.close()
			self.bFileOpen = True
		except EOFError:
			# Not a valid File =/
			print "Not a valid file"
			self.clear()
		
		# Bereite Workspace vor
		self.prepareWorkspace()
		
	def close(self):
		if self.bFileOpen == True:
			self.oFile = open(self.sFilepath, 'wb')
			cPickle.dump(self.aWorkspaceData, self.oFile)
			self.oMain.builder.get_object("nmrOverviewStore").clear()
			# Dateizeiger schliessen
			self.oFile.close()
			# Objekt cleanen
			self.clear()
			
	def prepareWorkspace(self):
		"""Hier wird eigentlich alles gemacht."""
		print "Aktiviere Widgets..."
		self.oMain.builder.get_object("gNMR_workspace").set_sensitive(True)
		self.oMain.builder.get_object("mainwidgetscontainer_paned").set_sensitive(True)
		
		# Nach dem Laden sollten die NMRs aktualisiert werden.
		self.refreshNMRsCache()
		
	def refreshNMRsCache(self):
		""" Aktualisiert den NMR-Cache. Dazu läuft es durch nmrpath und vergleicht den Inhalt mit nmrs """
		# Wenns den Unterpfad noch nicht gibt - erstellen.
		if "nmrs" not in self.aWorkspaceData:
			self.aWorkspaceData["nmrs"] = {}
		
		# Und los gehts
		dirs = os.listdir(os.path.join(self.getKey('nmrpath'), 'nmr'))
		for dir in dirs:
			if os.path.isdir(os.path.join(self.getKey('nmrpath'), 'nmr', dir)):
				self.refreshSingleNMRCache(dir)
				
		for nmrpar in self.aWorkspaceData["nmrs"]:
			piter = self.oMain.builder.get_object("nmrOverviewStore").append(None, [nmrpar, '', 'gtk-directory'])
			for nmrchi in self.aWorkspaceData["nmrs"][nmrpar]:
				self.oMain.builder.get_object("nmrOverviewStore").append(piter, [nmrchi, self.aWorkspaceData["nmrs"][nmrpar][nmrchi].getExperiment(), 'gtk-directory'])
				
	def refreshSingleNMRCache(self, dir):
		subdirs = os.listdir(os.path.join(self.getKey('nmrpath'), 'nmr', dir))
		for subdir in subdirs:
			if os.path.isdir(os.path.join(self.getKey('nmrpath'), 'nmr', dir, subdir)):
				# Hack, damit nur echte Verzeichnisse genommen werden
				# Benötigt überarbeitung und flexibilität!
				fidPath = os.path.join(self.getKey('nmrpath'), 'nmr', dir, subdir, 'fid')
				if os.path.exists(fidPath) and os.path.isfile(fidPath):
					self.addSingleNMR(dir, subdir)
				else:
					# Kein echtes Verzeichnis
					pass
	
	def addSingleNMR(self, dir, subdir):
		print "Adding NMR... %s, %s" % (dir, subdir)
		if dir not in self.aWorkspaceData["nmrs"]:
			# Elternverzeichnis in die Liste einfügen
			self.aWorkspaceData["nmrs"][dir] = {}
			print "Mainpath added: %s" % (dir, )

		nmrpath = os.path.join(os.path.join(self.getKey('nmrpath'), 'nmr', dir, subdir))
		self.aWorkspaceData["nmrs"][dir][subdir] = nmr.getNMR(nmrpath)
		
	def setKey(self, key, value):
		self.aWorkspaceData[key] = value
	
	def getKey(self, key):
		if key in self.aWorkspaceData:
			return self.aWorkspaceData[key]
		else:
			return None
			
	def onRowActivated(self, treeview, path, treeviewColumn):
		(model, row) = treeview.get_selection().get_selected()
		if model.iter_parent(row) is not None:
			print "Has a parent, is a NMR."
			info_chi = model.get(row, 0)[0]
			info_par = model.get(model.iter_parent(row), 0)[0]
			print info_chi, info_par
			
			# Pixmal clearen.
			self.pixmap.clear()
			
			# NMR kopieren - wir wollen die Dateien nicht mit unnötigen Informationen füllen
			self.loadedNMR = copy.copy(self.aWorkspaceData["nmrs"][info_par][info_chi])
			self.loadedNMR.doFFT()
			
			self.pixmap.setNMR(self.loadedNMR)
			self.pixmap.plot()
			
	def configureDrawingArea(self, widget, event):
		x, y, width, height = widget.get_allocation()
		
		self.pixmap = Workspace_plotArea(widget, width, height)
		
		if isinstance(self.loadedNMR, nmr.nmr_prototype.NMR):
			self.pixmap.plot()
		
		return True
		
	def exposeDrawingArea(self, widget, event):
		x , y, width, height = event.area
		
		self.pixmap.refresh(x, y, width, height)
		
		if isinstance(self.loadedNMR, nmr.nmr_prototype.NMR):
			self.pixmap.plot()
			
		return False
		
		
class Workspace_plotArea(gtk.gdk.Pixmap):
	data = {
		'x_val' : [],
		'y_val' : [],
	}
	
	# Fenster-Parameter
	iWidth = 0
	iHeight = 0
	
	oWidget = None
	oNMR = None
	
	padding = 5
	x_axis_pos = 30
	spectra_space_bottom = 30
	spectra_space_top = 100
	
	x_axis_labels = 20
	x_axis_sublabels = 10 # Num of steps _with_ the start. 10 means there are 9 additional marks between two main labels
	
	def __init__(self, widget, width, height):
		super(Workspace_plotArea, self).__init__(widget.window, width, height)
		
		self.iWidth = width
		self.iHeight = height
		self.oWidget = widget
		
		# Weisse Fläche initialisieren
		self.draw_rectangle(self.oWidget.get_style().white_gc, True, 0, 0, self.iWidth, self.iHeight)
		
	def setNMR(self, nmr):
		self.oNMR = nmr
		
	def refresh(self, x = 0, y = 0, width = None, height = None):
		if width == None:
			width = self.iWidth
		if height == None:
			height = self.iHeight
		self.oWidget.window.draw_drawable(self.oWidget.get_style().fg_gc[gtk.STATE_NORMAL], self, x, y, x, y, width, height)
		
	def clear(self):
		self.draw_rectangle(self.oWidget.get_style().white_gc, True, 0, 0, self.iWidth, self.iHeight)
		self.oWidget.window.draw_drawable(self.oWidget.get_style().fg_gc[gtk.STATE_NORMAL], self, 0, 0, 0, 0, self.iWidth, self.iHeight)
		self.data = {
			'x_val' : [],
			'y_val' : [],
		}
		self.oNMR = None
		
	def plot(self):
		values = self.oNMR.getValues()
		self.data['y_val'] = values[0]
		if len(values) == 2:
			self.data['x_val'] = values[1]
		else:
			self.data['x_val'] = range(0, len(self.data['x_val']))
			
		# Zeichne x-Achse
		self.drawXAxis()
			
		# Berechne die Maximal-Höhe für das Zeichnen
		self.spectraHeight = self.iHeight - 2*self.padding - self.x_axis_pos - self.spectra_space_bottom - self.spectra_space_top
		self.spectraWidth = self.iWidth - 2*self.padding
		
		if self.spectraHeight > 0:
			# Nur zeichnen, wenn überhaupt Platz vorhanden ist.
			# Suche Maximum und Minimum für y-Werte
			y_min = min(self.data['y_val'])[0]
			y_max = max(self.data['y_val'])[0]
			
			# Berechne Differenz
			y_dif = y_max - y_min
			
			# Das selbe für x:
			x_dif = len(self.data['y_val'])
			
			# Berechne das Mapping Pixel => y
			self.zoom_y = float(self.spectraHeight) / y_dif
			self.zoom_x = float(self.spectraWidth) / x_dif
			
			# Zeichne Plot
			self.drawPlot()
			self.flagXAxis()
		
		self.refresh()
		
	def drawXAxis(self):
		# Zeichnet die Achsen-Linie
		self.draw_line(self.oWidget.get_style().black_gc, self.x(0), self.y(self.x_axis_pos), self.x(0, True), self.y(self.x_axis_pos))
		
	def flagXAxis(self):
		specWidth = self.oNMR.getSpectralWidth()
		specLeftPPM = self.oNMR.getOffset()
		specRightPPM = specLeftPPM - specWidth
		resolution = len(self.data['y_val']) / specWidth
		diffPPM = specWidth - specLeftPPM
		
		if specWidth / self.x_axis_labels < 1:
			steps = math.floor(specWidth)
		else:
			steps = math.floor(specWidth / self.x_axis_labels)
		
		specLeftPPMMark = math.ceil(specLeftPPM) if specLeftPPM < 0 else math.floor(specLeftPPM)
		specRightPPMMark = math.ceil(specRightPPM) if specRightPPM < 0 else math.floor(specRightPPM)
		
		for i in range(int(specLeftPPMMark), int(specRightPPMMark), int(-1*steps)):
			x = ((i + diffPPM) * resolution) * self.zoom_x
			print x, i
			points = (
				(self.x(x), self.y(self.x_axis_pos), ), 
				(self.x(x), self.y(self.x_axis_pos - 5), )
			)
			
			self.draw_lines(self.oWidget.get_style().black_gc, points)
		
		
	def drawPlot(self):
		# Lege mit dem Polygon los.
		points = []
		
		x = 0
		for y in self.data['y_val']:
			x += 1
			# Appendiere x und y
			if isinstance(y, tuple):
				y = y[0]
			coords = (int(self.x(x * self.zoom_x)), int(self.y(y * self.zoom_y + self.x_axis_pos + self.spectra_space_bottom)))
			points.append(coords)
			
		a = self.draw_lines(self.oWidget.get_style().black_gc, points)
			
	def x(self, x, reversed = False):
		if reversed:
			return int(round(self.iWidth - (x + self.padding)))
		else:
			return int(round(x + self.padding))
		
	def y(self, y, reversed = False):
		if reversed:
			return int(round(y + self.padding))
		else:
			return int(round(self.iHeight - (y + self.padding)))
